//预处理:在使用库函数之前，我们需要使用#include引入对应的头文件。这种以#开头的命令被称之为"预处理命令"。
//C语言提供了多种预处理功能，如宏定义、文件包含、条件编译等。合理使用预处理功能编写的程序更便于阅读、修改、移植和调试，也有利于模块化程序设计。

/*
 * https://blog.csdn.net/qq_41865229/article/details/86746707
 * 文件包含:
 * 比如#include <stdio.h>这样的就是文件包含，其作用就是将其他文件的内容引入到此C程序当中——与宏定义一样是纯粹的文本替换。
 * 比如我们常用的printf函数就是从该<stdio.h>文件中而来。而这种以.h作为后缀的我们称之为"头文件"。
 *
 * 语法: #include <文件名> 或 #include "文件名"
 * TIPS:
 * ·使用尖括号<>表示在"开发环境的头文件目录(系统的头文件目录-标准C安装的目录)"下查找，而不在"源文件目录(也就是当前项目所在目录)"去查找
 * ·使用双引号""则表示首先在"当前的源文件目录(本工程目录)"中查找，如果没有找到才到"开发环境的头文件目录"去查找
 * · "文件名" 可以是相对路径，也可以是绝对路径，建议不要使用绝对路径。
 */
//#include <stdio.h> 你当然可以使用""替换掉<>，但是根据其查找的原理，会导致编译的时间变长。
#include "stdio.h"
//自定义一个头文件，并通过#include引入进来。
#include "./pretreatment.h"
#include "other.h"

/*
 * 宏(macro)定义:
 * 1、在C语言源程序中允许用一个标识符来表示一个"字符串"，称为"宏"。被定义为"宏"的标识符称为"宏名"。
 * 2、在编译预处理时由"预处理器"对程序中所有出现的"宏名"，都会用宏定义中的字符串去进行"文本替换"，这被称为"宏代换"或"宏展开"。
 */

//无参宏: 语法: #define 标识符 字符串(可以是常量也可以是表达式)
#define PI 3.14
#define EXP x * 10 * 1.5

//有参宏: 语法: #define 宏名(形参列表) 字符串
//有参宏类似于函数，也有形参与实参之分。带参数的宏，在调用中，不仅要宏展开，还要用实参去代换形参。
#define F(x) x*x*x
//注意事项: 当"字符串"是表达式时，建议表达式中的变量使用()包含起来，因为在预处理阶段展开宏时，实参可以会用表达式替换宏定义中的变量
#define F2(x) (x)*(x)*(x)

#define test 0;

//换行符"\"。我们定义宏的时候若宏的内容很多，我们也不可能总是一条语句就写完，此时为了美观，我们可以用换行符。
//          定义宏时，若有多条语句时，我们在每行的末尾(除了最后一行)加上"\"，就代表着换行的意思。
#define Print printf("这是第1条语句\n");\
              printf("这是第2条语句\n");

#define Show(str1, str2, str3)\
{\
    printf("%s\n",str1);\
    printf("%s\n",str2);\
    printf("%s\n",str3);\
}

//字符串化符"#"。"#"是字符串化的意思，我们可以在宏定义中使用"#"符号将跟在后面的参数转换成一个字符串。
#define Print2(str)\
{\
    printf(#str"的值是%d\n",str);\
}

//定义一个全局变量
int otherPlaceValue = 1000;

//extern声明函数时，只要带形参类型即可，不一定需要形参名
extern void func(int);

int main() {
    //TIPS:我们可以通过在控制台输出编译命令 gcc -E 打算预处理的文件名.c -o 生成文件名.i 比如此文件main.c编译成main.i文件
    /*
     * 生成的main.i文件中有这样的内容：
     * printf("使用定义的宏PI:%f\n", 3.14);
     * float test = 10 * x * 10 * 1.5; —— 可以看到了x还是出现了，然而如果实际编译运行的话由于x根本没定义将会直接报错，那么为什么预处理能够顺利通过？
     * 因为宏定义只是简单的文本替换，不会做语法检查!!
     */
    printf("使用定义的无参宏PI: %f\n", PI);
    //测试生成预处理文件时再开起来，否则编译出错
//    float test = 10 * EXP;
    printf("使用定义的带参宏F(): %d\n", F(3));
    //因为宏展开是纯粹的字符串替换，所以替换后实际上是这样的: 1+2*1+2*1+2*1+2
    printf("带参宏的变量不使用()的隐患——当实参传表达式时: %d\n", F(1 + 3));
    printf("带参宏的变量使用()时: %d\n", F2(1 + 3));

    //无参多行宏函数
    Print;
    //带参数多行宏函数
    Show("first", "second", "third");
    //字符串化宏函数
    Print2(2 + 90);

    printf("通过自定义头文件引入的宏:%s\n", HELLO_WORLD);

    //使用C语言默认预定义的宏
    printf("显示所在行号:%d\n", __LINE__);
    printf("显示所在函数:%s\n", __func__);
    printf("显示所在函数:%s\n", __FUNCTION__);
    printf("显示当前时间:%s\n", __TIME__);
    printf("显示当前日期:%s\n", __DATE__);
    printf("显示正在被编译的源文件名:%s\n", __BASE_FILE__);
    printf("显示所处当前文件名:%s\n", __FILE__);
    printf("显示所处文件的包含层数，从0开始:%d\n", __INCLUDE_LEVEL__);
    printf("编译器遵循ANSI C标准时该标识被赋值为1:%d\n", __STDC__);

#line 100
    printf("__LINE__扩展用法(从当前位置开始重新计算行数):%d\n", __LINE__);

    //调用一个来自外部函数
    func(47);
    //来自引用的头文件中引用的头文件中引用的变量
    printf("来自引用的头文件中引用的头文件引用的extern变量:%d\n", globalVariables2);
    printf("来自引用的头文件中引用的头文件引用的extern(隐式)变量:%d\n", globalVariables3);
    //测试extern和定义的区别时打开
//    printf("来自引用的头文件中引用的头文件引用的extern变量:%d\n", globalVariables4);

    /*
     * 条件编译——满足条件才进行编译:
     * #        空指令，无任何效果
     * #if      如果条件为真，则对相应程序段进行编译
     * #elif    如果前面条件为假，而该条件为真，则对相应程序段进行编译
     * #else    如果前面条件均为假，则对相应程序段进行编译
     * #ifdef   如果该宏已定义，则对相应程序段进行编译
     * #ifndef  如果该宏没有定义，则对相应程序段进行编译
     * #undef   在此程序中，取消在先前程序中已定义的宏。
     * #endif   结束相应的条件编译指令——当使用if条件编译时必须有，一个if对应一个endif。
     */
#

#ifdef test
    {
        printf("如果该宏定义了，则这段代码将会被编译\n");
    }
#else
    {
        printf("如果该宏未定义了，则这段代码不会被编译");
        由于该语法与if-else if-else语法类似，所以其他的就不举例了
    }
#endif

#if 0
    所有可以变相地通过#if 0来达成一种对代码进行注释的效果。
    比如当前这段话并未用注释符标注起来却不会因此而编译报错。
#endif

#ifndef NODEF
#undef PI
#ifdef PI
    printf("由于PI的预处理器定义被取消，所以这段代码不会被编译\n");
#endif
#ifndef PI
    printf("同理，由于PI的预处理器定义被取消，所以这段代码会被编译并运行\n");
#endif
#endif
    return 0;
}
